#include "porridgedb.h"
#include "pgwb.h"
#include "pgresult.h"

using namespace Porridge;

PorridgeDB::~PorridgeDB()
{
    if(m_connection)
        PQfinish(m_connection);
}

bool PorridgeDB::connect()
{
    if(m_connectionInfo.empty() || m_newSessionQuery.empty())
    {
        logError("Mandatory attribute not set");
        return false;
    }

    m_connection = PQconnectdb(m_connectionInfo.c_str());
    if (PQstatus(m_connection) == CONNECTION_OK)
    {
        m_indexator.setConnectionInfo(m_connectionInfo);
        return true;
    }

    logConnectionError("EchelonDB connection failed");
    PQfinish(m_connection);
    m_connection = NULL;
    return false;
}

bool PorridgeDB::setDBSizeLimit(long dbSizeLimit)
{
    if (PQstatus(m_connection) != CONNECTION_OK || m_setDBSizeLimitQuery.empty())
        return false;

    std::string sizeLimit = boost::lexical_cast<std::string>(dbSizeLimit);
    PGResult res = PQexec(m_connection, ("select * from " +
                                         m_setDBSizeLimitQuery + "(" + sizeLimit + ")").c_str());
    if (PQresultStatus(res) != PGRES_TUPLES_OK)
    {
        logConnectionError("EchelonDB query failed: " + m_setDBSizeLimitQuery);
        return false;
    }
    return true;
}

size_t PorridgeDB::startSession()
{
    if (PQstatus(m_connection) != CONNECTION_OK)
        return 0;

    PGResult res = PQexec(m_connection, ("select * from " + m_newSessionQuery + "()").c_str());
    if (PQresultStatus(res) != PGRES_TUPLES_OK)
    {
        logConnectionError("EchelonDB query failed: " + m_newSessionQuery);
        return 0;
    }
    m_tableID = std::string(PQgetvalue(res, 0, 0));
    return boost::lexical_cast<size_t>(m_tableID);
}

bool PorridgeDB::queryFailed(const std::string & query, bool result) const
{
    logConnectionError("EchelonDB query failed: " + query);
    return result;
}

void PorridgeDB::logConnectionError(const std::string & errorMessage) const
{
    logError(std::string("PostgreSQL error: ") + PQerrorMessage(m_connection));
    logError(errorMessage);
}

void PorridgeDB::logError(const std::string & errorMessage) const
{
    std::cerr << errorMessage << "\n";
}

bool Porridge::PorridgeDB::tableBegin(const std::string &tableName) const
{
    //prepare SQL COPY-command
    std::string query = "copy " + tableName + "_" + m_tableID + " from stdin with binary;";

    //exec COPY command, begin transaction
    PGResult res(PQexec(m_connection, query.c_str()));
    if (PQresultStatus(res) != PGRES_COPY_IN)
        return queryFailed(query);

    PGWB::writeTableHeader(this);

    return true;
}

bool Porridge::PorridgeDB::tableEnd() const
{
    PGWB::writeTableTrailer(this);

    //end transaction
    if(PQputCopyEnd(m_connection, NULL) == -1)
        logConnectionError("PorridgeDB write sessions error");
    PGResult res = PQgetResult(m_connection);
    if (PQresultStatus(res) != PGRES_COMMAND_OK)
        return queryFailed("PQputCopyEnd");

    return true;
}

PorridgeDB& operator<<(PorridgeDB& out, const std::string & data) // output
{
    out.write(data.data(), data.size());
    return out;
}

void PorridgeDB::write(const char *data, size_t length) const
{
    PQputCopyData(m_connection, data, length);
}

void PorridgeDB::createIndexes()
{
    m_indexator(m_tableID);
}

void PorridgeDB::setConnetctionInfo(const std::string &connectionInfo)
{
    m_connectionInfo = connectionInfo;
}

void Porridge::PorridgeDB::setQueryThreadLimit(size_t limit)
{
    m_indexator.setQueryThreadLimit(limit);
}

void Porridge::PorridgeDB::setNewSessionQuery(const std::string &query)
{
    m_newSessionQuery = query;
}

void Porridge::PorridgeDB::setDBSizeLimitQuery(const std::string &query)
{
    m_setDBSizeLimitQuery = query;
}

void Porridge::PorridgeDB::setMaxBufferSize(size_t size)
{
    maxBufferSize = size;
}

void PorridgeDB::appendProcessingQuery(const std::string &query)
{
    m_indexator.appendProcessingQuery(query);
}
